import json
import allure
import jsonpath


from common.sendrequest import SendRequest
from common.debugtalk import DebugTalk
from common.recordlog import logs
from common.readyaml import ReadYamlData
from common.assertions import Assertions

import requests
from requests import utils


class RequestBase:
    def __init__(self):
        self.read = ReadYamlData()
        self.run = SendRequest()
        self.asserts = Assertions()
    """
    替换testcase的yaml文件中的部分数据
    
    入参为字典：
          json:
                pro_id: ${get_extract_data(goodsIds,1)}
                page: 1
                size: 20
    """
    def replace_load(self,data):
        str_data = data
        logs.info(f"获取到需要替换的yaml文件数据：{str_data}")
        try:
            # 将字典转化为字符串，便于后续的字符串处理操作
            if not isinstance(str_data,str):
                str_data = json.dumps(str_data)
            # 判断入参中有多少处地方需要进行值替换
            for i in range(str_data.count("$")):
                # 判断需要替换的值的格式是否正确
                if "$" and "}" in str_data:
                    start_index = str_data.index("$")
                    end_index = str_data.index("}",start_index)
                    # 取出${get_extract_data(goodsIds,1)}
                    replace_data = str_data[start_index:end_index+1]
                    # 取出get_extract_data
                    func_name = replace_data[2:replace_data.index("(")]
                    # 取出goodsIds,1
                    func_params = replace_data[replace_data.index("(")+1:replace_data.index(")")]
                    # 动态调用函数 将func_params字符串转换为列表并解包提供给函数使用 得到extract.yaml文件中的数据
                    # *解包列表 | **解包字典
                    extract_data = getattr(DebugTalk(),func_name)(*func_params.split(",") if func_params else "")
                    # 将从extract.yaml文件中的得到的数据替换进str_data
                    str_data = str_data.replace(replace_data,str(extract_data))
                    logs.info(f"yaml文件数据已成功替换 替换后数据为：{str_data}")
                else:
                    logs.error(f"yaml文件数据替换出错，请检查格式 数据为：{str_data}")
            # 若data原本为字典格式，则将替换后的数据转换为字典格式
            if data and isinstance(data,dict):
                data = json.loads(str_data)
            else:
                data = str_data
            logs.info(f"yaml文件数据已全部替换 数据为：{data}")
            return data
        except Exception as e:
            logs.error(f"替换yaml文件数据时出错 异常：{e}")


    # 将接口响应数据转化为字符串
    @staticmethod
    def allure_attach_response(response):
        if isinstance(response, dict):
            allure_response = json.dumps(response, ensure_ascii=False, indent=4)
        else:
            allure_response = response
        return allure_response


    def extract_data(self, testcase_extract, res_text,res_cookies):
        """
        提取接口的返回值，支持正则表达式和json提取器
        :param res_cookies: 响应值的cookies
        :param res_text: 响应值文本
        :param testcase_extract: testcase文件yaml中的extract值
        :return:
        """

        """
              extract_list:
#                           
                            goodsId: $.goodsList[*].goodsId
        """
        try:
            extract_res_data = None
            for key,value in testcase_extract.items():
                # 需要写入的数据为cookies
                if key == "cookies":
                    # cookie_data = res_cookies
                    cookie_data = requests.utils.dict_from_cookiejar(res_cookies)
                    cookie_str = "; ".join([f"{k}={v}" for k, v in cookie_data.items()])
                    logs.info(f"cookie_data:{cookie_data}")
                    if cookie_str is not None:
                        extract_res_data = cookie_str
                # 需要写入的数据为json方法可提取的数据，如$..message
                elif "$" in value:
                    extract_res_data = jsonpath.jsonpath(json.loads(res_text), value)[0]
                else:
                    logs.error(f"提取参数失败 响应值：{res_text} 响应cookies:{res_cookies}")
                if extract_res_data is not None:
                    extract_data = {key: extract_res_data}
                    logs.info(f"extract_data:{extract_data}")
                    self.read.write_extract_yaml(extract_data)
                    logs.info(f"接口返回值：{extract_data}已写入extract.yaml文件中")
                else:
                    logs.error(f"提取参数为空，请检查 响应值：{res_text}")
        except Exception as e:
            logs.error(f"提取接口返回值写入extract.yaml文件失败，异常：{e}")

    def specification_yaml(self, base_info, test_case, client):
        """
        接口请求处理基本方法
        :param base_info: yaml文件里面的baseInfo
        :param test_case: yaml文件里面的testCase
        :return:
        """
        try:
            params_type = ['data', 'json', 'params']

            api_name = base_info['api_name']
            allure.attach(api_name, f'接口名称：{api_name}', allure.attachment_type.TEXT)
            url = base_info['url']
            allure.attach(api_name, f'接口地址：{url}', allure.attachment_type.TEXT)
            method = base_info['method']
            allure.attach(api_name, f'请求方法：{method}', allure.attachment_type.TEXT)
            header = None
            if "header" in base_info:
                header = self.replace_load(base_info['header'])
            allure.attach(api_name, f'请求头：{header}', allure.attachment_type.TEXT)
            # 处理cookie
            cookie = None
            if base_info.get('cookies') is not None:
                cookie = eval(self.replace_load(base_info['cookies']))
            case_name = test_case.pop('case_name')
            allure.attach(api_name, f'测试用例名称：{case_name}', allure.attachment_type.TEXT)
            # 处理断言
            val = self.replace_load(test_case.get('validation'))
            test_case['validation'] = val
            validation = eval(test_case.pop('validation'))
            # 处理参数提取
            extract = test_case.pop('extract', None)

            # 处理文件上传接口
            file, files = test_case.pop('files', None), None
            if file is not None:
                for fk, fv in file.items():
                    allure.attach(json.dumps(file), '导入文件')
                    files = {fk: open(fv, mode='rb')}

            # 处理接口的请求参数
            if test_case == {}:
                res = self.run.run_main(client, name=api_name, url=url, case_name=case_name, header=header,
                                        method=method,
                                        file=files, cookies=cookie)
                logs.info(f"请求参数为空，不传入请求参数")
            else:
                for key, value in test_case.items():
                    if key in params_type:
                        test_case[key] = self.replace_load(value)
                res = self.run.run_main(client, name=api_name, url=url, case_name=case_name, header=header,
                                        method=method,
                                        file=files, cookies=cookie, **test_case)

            content_type = res.headers.get("Content-Type", "").lower()
            # 若返回结果为html数据，则判断title值是否和yml文件中记录的title一致
            if "html" in content_type and not ("json" in content_type or res.text.lstrip().startswith("{")):
                logs.error(f"接口返回内容为真实HTML内容，请确认是否有误")  # 真实HTML才报错
                try:
                    title_text = validation[0]["html_title"]["title"]
                    assert f"<title>{title_text}</title>" in res.text
                except Exception as e:
                    logs.error(f"判断html数据title是否一致失败，请检查yml文件中title字段，异常：{e}")
                    assert False

            else:
                status_code = res.status_code
                logs.info(f"接口响应状态码为{status_code}")
                allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)
                if extract is not None:
                    self.extract_data(extract, res.text, res.cookies)
                # 处理断言
                res_json = json.loads(res.text)  # 把json格式转换成字典字典
                self.asserts.assert_result(validation, res_json, status_code)
        except Exception as e:
            logs.error(e)
            raise e






